#include "lib/pvmsystem.h"
#include "pvm/pvmapi.h"
#include "lib/pvmstrings.h"
#include "lib/pvmassert.h"

#include <time.h>

#include <stdio.h>
#include <signal.h>

#include <s3eDevice.h>
#include <s3eTimer.h>
#include <s3eThread.h>

#include <IwUTF8.h>
#include <IwDebug.h>

#include "lib/memory/pvmmemory.h"

#if defined(PVM_USE_SPINLOCK_AS_CRITICAL)
#include "lib/sys/common/pvmspinlock.h"
#endif //defined(PVM_USE_SPINLOCK_AS_CRITICAL)

static PVMCriticalSection mmgrCriticalInstance;

PVMCriticalSection* PVMGetCriticalForMmgr()
{
	PVMInitCritical(&mmgrCriticalInstance);

	return &mmgrCriticalInstance;
}

void PVMFreeCriticalForMmgr()
{
	PVMDeinitCritical(&mmgrCriticalInstance);
}

#ifdef I3D_ARCH_X86

pvm_bool PVMIsThreadsAvailable()
{
	return s3eThreadAvailable();
}

#ifndef PVM_USE_SPINLOCK_AS_CRITICAL

pvm_bool PVMInitCritical(PVMCriticalSection *critical)
{
	*critical = NULL;
	if(!PVMIsThreadSyncAvailable())
		return false;
	*critical = s3eThreadLockCreate();
	return *critical!=NULL;
}

void PVMDeinitCritical(PVMCriticalSection *critical)
{
	if(!PVMIsThreadSyncAvailable())
		return;

	if(critical==NULL ||*critical==NULL)
		return;
	s3eThreadLockDestroy(*critical);
	*critical = NULL;
}

pvm_bool PVMEnterCritical(PVMCriticalSection *critical)
{
	if(!PVMIsThreadSyncAvailable() || !critical || !*critical)
		return false;
	return s3eThreadLockAcquire(*critical,  -1)==S3E_RESULT_SUCCESS;
}

void PVMLeaveCritical(PVMCriticalSection *critical)
{
	if(!PVMIsThreadSyncAvailable() || !critical || !*critical)
		return;
	
	s3eResult result = s3eThreadLockRelease(*critical);
}

#endif//PVM_USE_SPINLOCK_AS_CRITICAL

void PVMInitThreadVar(PVMThreadHandle* outHandle)
{
	if(!outHandle)
		return;

	*outHandle = NULL;
}

pvm_bool PVMIsThreadValid(PVMThreadHandle threadHandle)
{
	if(!PVMIsThreadsAvailable())
		return false;

	if(threadHandle==NULL)
		return false;

	return true;
}

PVMThreadHandle PVMGetCurrentThread()
{
	if(!PVMIsThreadsAvailable())
		return 0;
	return s3eThreadGetCurrent();
}

pvm_bool PVMCreateThread(PVMThreadHandle* outHandle, PVMThreadStartRoutine routine, void *param)
{
	if(!PVMIsThreadsAvailable() || !outHandle)
		return false;

	*outHandle = s3eThreadCreate((PVMThreadStartRoutine)routine, param) ;
	return *outHandle!=NULL;
}

void PVMSuspendThread(PVMThreadHandle threadHandle)
{
	//TODO:realize
}

void PVMResumeThread(PVMThreadHandle threadHandle)
{
	//TODO:realize
}

int PVMJoinThread(PVMThreadHandle threadHandle)
{
	if(s3eThreadJoin(threadHandle)==S3E_RESULT_SUCCESS)
		return 0;
	return -1;
}

#else //!I3D_ARCH_X86

#ifndef PVM_USE_SPINLOCK_AS_CRITICAL

pvm_bool PVMIsThreadSyncAvailable()
{
	return PVMIsThreadsAvailable();
}

pvm_bool PVMInitCritical(PVMCriticalSection *critical)
{
	*critical = NULL;
	//TODO:realize
	return true;
}

void PVMDeinitCritical(PVMCriticalSection *critical)
{
	*critical = 0;
	//TODO:realize
}

pvm_bool PVMEnterCritical(PVMCriticalSection *critical)
{
	//TODO:realize
	return true;
}

void PVMLeaveCritical(PVMCriticalSection *critical)
{
	//TODO:realize
}

#endif//PVM_USE_SPINLOCK_AS_CRITICAL

void PVMInitThreadVar(PVMThreadHandle* outHandle)
{
	*outHandle = NULL;
	//TODO:realize
}

pvm_bool PVMIsThreadValid(PVMThreadHandle threadHandle)
{
	//TODO:realize
	return false;
}

PVMThreadHandle PVMGetCurrentThread()
{
	//TODO:realize

	return 0;
}

pvm_bool PVMCreateThread(PVMThreadHandle* outHandle, PVMThreadStartRoutine routine, void *param)
{
	//TODO:realize
	return false;
}

void PVMSuspendThread(PVMThreadHandle threadHandle)
{
	//TODO:realize
}

void PVMResumeThread(PVMThreadHandle threadHandle)
{
	//TODO:realize
}

int PVMJoinThread(PVMThreadHandle threadHandle)
{
	//TODO:realize
	return 0;
}

#endif//I3D_ARCH_X86

#if defined(PVM_USE_SPINLOCK_AS_CRITICAL)

#if defined(WINAPI_FAMILY)

void* PVMGetThreadMarker()
{
	return 0;
}

#elif defined(I3D_ARCH_X86)

extern "C" 
{
	unsigned long  __stdcall GetCurrentThreadId(void);
}

void* PVMGetThreadMarker()
{
	return (void*)GetCurrentThreadId();
}
#else
void* PVMGetThreadMarker()
{
	//asume that any thread has unique stack limit
	void* result;

	asm volatile (
		"mov	%[result], sl \n\t"
		:[result]"=r"(result) //output argument
		:
		:
		);

	return result;
}
#endif

pvm_bool PVMIsThreadSyncAvailable()
{
	return true;
}


pvm_bool PVMInitCritical(PVMCriticalSection *critical)
{
	if(critical == NULL)
		return false;

	PVMSpinLockInit(critical->lock);
	critical->marker = 0;

	return true;
}

void PVMDeinitCritical(PVMCriticalSection *critical)
{
	if(critical == NULL)
		return;

	pvm_assert(critical->lock==0);

	critical->lock = 0;
	critical->marker = 0;

}

pvm_bool PVMEnterCritical(PVMCriticalSection *critical)
{
	if(critical==NULL)
		return false;

	PVMSpinLockAcquire(critical->lock);
	pvm_assert(critical->marker==0);
	critical->marker = PVMGetThreadMarker();
	return true;
}

void PVMLeaveCritical(PVMCriticalSection *critical)
{
	if(critical==NULL)
		return;
	critical->marker = 0;
	PVMSpinLockRelease(critical->lock);
}

#endif //PVM_USE_SPINLOCK_AS_CRITICAL

void PVMSleep(pvm_uint32 sleepTime)
{
	s3eDeviceYield(sleepTime);
}

pvm_uint32 PVMGetMilliSecondsCount()
{
	return (pvm_uint32)s3eTimerGetUTC();
}

PVMFileHandle PVMFileOpen(const pvm_char* fileName, pvm_int32 mode)
{
  char modeStr[32];
  modeStr[0] = 0;

  if(mode&PVM_READ && mode&PVM_WRITE && mode&PVM_OPEN_EXISTING)
  {
    strcpy(modeStr, "r+b");
  }
  else if(mode&PVM_READ && mode&PVM_WRITE && mode&PVM_CREATE_ALWAYS)
  {
    strcpy(modeStr, "w+b");
  }
  else if(mode&PVM_READ && mode&PVM_WRITE && mode&PVM_OPEN_ALWAYS)
  {
    strcpy(modeStr, "a+b");
  }
  else if(mode&PVM_READ && mode&PVM_OPEN_EXISTING)
  {
    strcpy(modeStr, "rb");
  }
  else if(mode&PVM_WRITE && mode&PVM_CREATE_ALWAYS)
  {
    strcpy(modeStr, "wb");
  }
  else if(mode&PVM_WRITE && mode&PVM_OPEN_ALWAYS)
  {
    strcpy(modeStr, "ab");
  }

  int utf8NameLength = IwWideCharToUTF8	((const ucs2char*)fileName, -1, NULL, 0)	;
  char* utf8Name = new char[utf8NameLength];
  if(utf8Name==NULL)
	return PVM_FILE_OPEN_ERROR;

  if(IwWideCharToUTF8((const ucs2char*)fileName, -1, utf8Name, utf8NameLength)	!= utf8NameLength)
  {
	  delete[] utf8Name; utf8Name = NULL;
	  return PVM_FILE_OPEN_ERROR;
  }

  FILE* result = fopen(utf8Name, modeStr);
  delete[] utf8Name; utf8Name = NULL;

  return result;
}

void PVMFileClose(PVMFileHandle file)
{
  fclose(file);
}

pvm_bool PVMFileRead(PVMFileHandle file, void* buffer, pvm_size nBytes, pvm_size* readResult)
{
  if(readResult==NULL)
    return false;

  *readResult = 0;

  if(nBytes==0)
    return true;
  size_t res = fread(buffer, 1, nBytes, file);
  if(res!=0)
  {
    *readResult = res;
    return true;
  }
  return ferror(file)==0;
}

pvm_bool PVMFileWrite(PVMFileHandle file, void* buffer, pvm_size nBytes, pvm_size* writeResult)
{
  if(writeResult==NULL)
    return false;

  *writeResult = 0;

  if(nBytes==0)
    return true;

     size_t res = fwrite(buffer, 1, nBytes, file);

   if(res!=0)
   {
     *writeResult = res;
     return true;
   }
   else
   {
     return false;
   }
}

pvm_file_size PVMFileSize(PVMFileHandle file)
{
  pvm_file_size curOffset = PVMFileTell(file);
  if(curOffset==PVM_INVALID_FILE_SIZE)
    return PVM_INVALID_FILE_SIZE;

  if(!PVMFileSeek(file, 0, EPVM_SEEK_END))
    return PVM_INVALID_FILE_SIZE;

  pvm_file_size size = PVMFileTell(file);

  PVMFileSeek(file, curOffset, EPVM_SEEK_BEGIN);

  return size;
}

pvm_bool PVMFileSeek(PVMFileHandle file, pvm_file_size pos, pvm_int32 origin)
{
  int whence = 0;
  switch(origin)
  {
  case EPVM_SEEK_BEGIN:
    whence = SEEK_SET;
    break;
  case EPVM_SEEK_CURRENT:
    whence = SEEK_CUR;
    break;
  case EPVM_SEEK_END:
    whence = SEEK_END;
    break;
  };

  return fseek(file, (long)pos, whence)==0;
}

pvm_bool PVMFileEOF(PVMFileHandle file)
{
	return feof(file);
}

pvm_file_size PVMFileTell(PVMFileHandle file)
{
	long offset = ftell(file);

	if(offset==-1)
		return PVM_INVALID_FILE_SIZE;

	return offset;
}

pvm_bool PVMInitEvent(PVMEventHandle *event)
{
	//TODO:realize
	return false;
}

void PVMDeinitEvent(PVMEventHandle *event)
{
	//TODO:realize
}

pvm_bool PVMWaitForEvent(PVMEventHandle *event)
{
	//TODO:realize
	return false;
}

void PVMSignalEvent(PVMEventHandle *event)
{
	//TODO:realize
}

pvm_bool PVMSystemInit()
{
	return true;
}

void PVMSystemDeinit()
{
}


void PVMSysLogOutString(const pvm_char* logString)
{
	char logStringUtf8[265];

	pvm_int logStringUtf8Length = IwWideCharToUTF8	((const ucs2char*)logString, -1, NULL, 0)	;
	if(logStringUtf8Length > (int) sizeof(logStringUtf8))
		logStringUtf8Length = sizeof(logStringUtf8);


	if(IwWideCharToUTF8((const ucs2char*)logString, -1, logStringUtf8, logStringUtf8Length)	!= logStringUtf8Length)
	{
		return;
	}

	IwTrace(PVPM, (logStringUtf8));
}